﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System.IO;

namespace NetOrientDB.ObjectModel
{
	public class OrientDocument
	{
		private IDictionary<string, object> _properties = new Dictionary<string, object>();

		public string Rid { get; set; }
		public string ClassName { get; set; }
		public string ReferenceRid
		{
			get
			{
				return "#" + this.Rid;
			}
		}

		public void Parse(JObject jObj)
		{
			this._properties = new Dictionary<string, object>();
			var propertiesParser = new PropertiesParser();
			this._properties = propertiesParser.Parse(jObj);
			if (this._properties.ContainsKey("@rid"))
			{
				this.Rid = this.GetProperty<string>("@rid").Replace("#", "");
			}
			if (this._properties.ContainsKey("@class"))
			{
				this.ClassName = this.GetProperty<string>("@class");
			}
		}
		public string GetJsonForInsert()
		{
			// TODO: Add Validations

			JObject props = new JObject();
			props.Add("@class", this.ClassName);
			props.Add("@rid", "-1:-1");
			props.Add("@version", 0);
			foreach (var key in this.GetPropertyKeys())
			{
				//props.Add(key, JToken.FromObject(this.GetProperty<object>(key)));
				props.Add(key, JToken.FromObject(this._properties[key]));
			}
			return props.ToString();
		}
		public string GetJsonForUpdate()
		{
			// TODO: Add Validations
			JObject props = new JObject();
			props.Add("@class", this.ClassName);
			props.Add("@rid", this.Rid);
			foreach (var key in this.GetPropertyKeys())
			{
				props.Add(key, JToken.FromObject(this.GetProperty<object>(key)));
			}
			return props.ToString();
		}
		public override string ToString()
		{
			if (!string.IsNullOrEmpty(this.ClassName))
			{
				return string.Format("{0}-{1}", this.ClassName, this.Rid);
			}
			else
			{
				return this.Rid;
			}
		}

		#region IPropertyContainer Implementation
		public T GetProperty<T>(string key)
		{
			if (!this._properties.Keys.Contains(key))
			{
				throw new ArgumentException(string.Format("Failed to retrieve property {0}", key));
			}
			return (T)Unbox<T>(this._properties[key]);
		}
		public T GetProperty<T>(string key, T defaultValue)
		{
			if (!this._properties.Keys.Contains(key)) return defaultValue;
			return (T)Unbox<T>(this._properties[key]);
		}
		public IEnumerable<string> GetPropertyKeys()
		{
			return this._properties.Keys.Where(o => !o.StartsWith("@"));
		}
		public bool HasProperty(string key)
		{
			return this._properties.ContainsKey(key);
		}
		public object RemoveProperty(string key)
		{
			var oldValue = this._properties.Keys.Contains(key) ? this._properties[key] : null;
			this._properties.Remove(key);
			return oldValue;
		}
		public void SetProperty<T>(string key, T value)
		{
			this._properties[key] = value;
		}
		#endregion

		private static object Unbox<T>(object o)
		{
			Type tType = typeof(T);
			if (tType.IsArray)
			{
 				throw new ArgumentException("Array not supported, please use IEnumerable<T>.");
			}
			Type to = o.GetType();
			if (to.IsArray)
			{
				return (T)o;
			}
			else if (typeof(T).IsGenericType && to.IsGenericType)
			{
				var innterType = typeof(T).GetGenericArguments()[0];
				Type genericListType = typeof(List<>).MakeGenericType(innterType);
				var list = (IList)Activator.CreateInstance(genericListType);
				foreach (var obj in ((IEnumerable<object>)o))
				{
					list.Add(Convert.ChangeType(obj, innterType));
				}
				return list;
			}			
			else
			{
				return Convert.ChangeType(o, typeof(T));
			}
		}

	}
}
